/*
 *  Copyright 2011-2012 Redia A/S
 * 
 *  This file is part of Redias Android Utilities.
 *
 *  Redias Android Utilities is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Redias Android Utilities is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with Redias Android Utilities.  If not, see <http://www.gnu.org/licenses/>.
 */
package dk.redia.lib.model;

import java.io.IOException;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.Vector;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import dk.redia.lib.log.Log;
import dk.redia.lib.utils.DateUtils;

public class InfoObject implements Parcelable {
	
	public static final int TYPE_ALL = 0x11111111;
	public static final int TYPE_UNKNOWN = 0;
	public static final int TYPE_STANDARD = 2;
	public static final int TYPE_ARRANGEMENT = 4;
	public static final int TYPE_POLL = 8;

	private static final int NEXT_TEXT_NONE = 0;
	private static final int NEXT_TEXT_HEADLINE = 1;
	private static final int NEXT_TEXT_CATEGORY = 2;
	private static final int NEXT_TEXT_ARRANGEMENT_END = 3;
	private static final int NEXT_TEXT_ARRANGEMENT_BEGIN = 4;
	private static final int NEXT_TEXT_BODY = 5;
	private static final int NEXT_TEXT_ID = 6;
	private static final int NEXT_TEXT_MEDIA_SOURCE_URL = 7;
	private static final int NEXT_TEXT_MEDIA_TYPE = 8;
	private static final int NEXT_TEXT_SUB_HEADLINE = 9;
	private static final int NEXT_TEXT_MEDIA_IN_THE_PICTURE = 10;
	private static final int NEXT_TEXT_MEDIA_TITLE = 11;
	private static final int NEXT_TEXT_MEDIA_ID = 12;
	private static final int NEXT_TEXT_META_DATA_KEY = 13;
	private static final int NEXT_TEXT_META_DATA_VALUE = 14;
	private static final int NEXT_TEXT_MEDIA_THUMB_URL = 15;
	private static final int NEXT_TEXT_MEDIA_DESCRIPTION = 16;
	private static final int NEXT_TEXT_LOCATION = 17;
	private static final int NEXT_TEXT_MEDIA_ARTIST = 18;
	private static final int NEXT_TEXT_MEDIA_YEAR = 19;
	// private static final int NEXT_TEXT_GEO_LOCATION = 20;
	private static final int NEXT_TEXT_GEO_LOCATION_NAME = 21;
	private static final int NEXT_TEXT_GEO_LOCATION_DESC = 22;
	private static final int NEXT_TEXT_GEO_LOCATION_STREET = 23;
	private static final int NEXT_TEXT_GEO_LOCATION_BUILDING = 24;
	private static final int NEXT_TEXT_GEO_LOCATION_POSTAL = 25;
	private static final int NEXT_TEXT_GEO_LOCATION_CITY = 26;
	private static final int NEXT_TEXT_GEO_LOCATION_LAT = 27;
	private static final int NEXT_TEXT_GEO_LOCATION_LNG = 28;
	private static final int NEXT_TEXT_ELEMENTREF = 29;
	private static final int NEXT_TEXT_URL = 30;
	private static final int NEXT_TEXT_CHANNEL_ID = 31;
	
	private static final String logTag = "InfoObject";
	
	private String category;
	private Map<String,String> headlines;
	private Map<String,String> subHeadlines;
	private String location;
	private int type;
	private Map<String, String> bodies;
	private Date arrangementStartTime;
	private Date arrangementEndTime;
	private String id;
	private String customerId;
	private String xmlSource;
	private List<Media> media;
	private List<String> channelIds;
	private List<String> childIds;
	private Map<String, String> metaData;
	private Map<String, Vector<GeoLocation>> geoLocations; 
	
	// Used during parsing
	private static Stack<String> tagStack = new Stack<String>();
	private static int nextText;
	
	public InfoObject() {
		this.category = "";
		this.headlines = new HashMap<String, String>();
		this.subHeadlines = new HashMap<String, String>();
		this.location = "";
		this.type = InfoObject.TYPE_UNKNOWN;
		this.bodies = new HashMap<String, String>();
		this.arrangementEndTime = new Date();
		this.arrangementStartTime = new Date();
		this.id = "";
		this.customerId = "";
		this.xmlSource = "";
		this.media = new ArrayList<Media>();
		this.childIds = new ArrayList<String>();
		this.channelIds = new ArrayList<String>();
		this.geoLocations = new HashMap<String, Vector<GeoLocation>>();
		this.metaData = new HashMap<String, String>();
	}
	
	public void writeToParcel(Parcel dest, int arg1) {
		dest.writeString(this.category);
		Bundle b = new Bundle();
		for (String language : this.headlines.keySet()) {
			b.putString(language, this.headlines.get(language));
		}
		dest.writeBundle(b);
		b = new Bundle();
		for (String language : this.subHeadlines.keySet()) {
			b.putString(language, this.subHeadlines.get(language));
		}
		dest.writeBundle(b);
		dest.writeInt(this.type);
		b = new Bundle();
		for (String language : this.bodies.keySet()) {
			b.putString(language, this.bodies.get(language));
		}
		dest.writeBundle(b);
		dest.writeInt(DateUtils.getUnixTime(this.arrangementEndTime));
		dest.writeInt(DateUtils.getUnixTime(this.arrangementStartTime));
		dest.writeString(this.id);
		dest.writeString(this.customerId);
		dest.writeString(this.xmlSource);
		dest.writeList(this.media);
		b = new Bundle();
		for (String language : this.metaData.keySet()) {
			b.putString(language, this.metaData.get(language));
		}
		dest.writeList(childIds);
		
		dest.writeBundle(b);
	}
	
	public static final Parcelable.Creator<InfoObject> CREATOR = new Parcelable.Creator<InfoObject>() {
		public InfoObject createFromParcel(Parcel source) {
			InfoObject io = new InfoObject();
			io.category = source.readString();
			io.headlines = new HashMap<String, String>();
			Bundle b = source.readBundle();
			for (String key : b.keySet()) {
				io.headlines.put(key, b.getString(key));
			}
			io.subHeadlines = new HashMap<String, String>();
			b = source.readBundle();
			for (String key : b.keySet()) {
				io.subHeadlines.put(key, b.getString(key));
			}
			io.type = source.readInt();
			io.bodies = new HashMap<String, String>();
			b = source.readBundle();
			for (String key : b.keySet()) {
				io.bodies.put(key, b.getString(key));
			}
			io.arrangementEndTime = DateUtils.fromUnixTimeStamp(source.readInt());
			io.arrangementStartTime = DateUtils.fromUnixTimeStamp(source.readInt());
			io.id = source.readString();
			io.customerId = source.readString();
			io.xmlSource = source.readString();
			io.media = new ArrayList<Media>();
			source.readList(io.media, null);
			io.metaData = new HashMap<String, String>();
			b = source.readBundle();
			for (String key : b.keySet()) {
				io.metaData.put(key, b.getString(key));
			}
			io.childIds = new ArrayList<String>();
			source.readList(io.childIds, null);
			return io;
		}
		
		public InfoObject[] newArray(int size) {
			return new InfoObject[size];
		}		
	};
	
	public static InfoObject fromXml(String xml) {
		InfoObject io = new InfoObject();
		io.xmlSource = xml;
		try {
			XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
	        factory.setNamespaceAware(false);
	        XmlPullParser xpp = factory.newPullParser();
	
	        nextText = NEXT_TEXT_NONE;
	        
	        xpp.setInput(new StringReader (xml));
	        
	        tagStack.clear();
	        
	        String currentLanguage = "";
	        //String currentMediaType = "";
	        Media currentMedia = null;
	        GeoLocation currentGeoLocation = null;
	        Vector<GeoLocation> currentGeoLocations = null;
	        String currentMetaDataKey = null;
	        int eventType;
	        while ((eventType = xpp.getEventType())!=XmlPullParser.END_DOCUMENT) {
	        	switch (eventType) {
	        	case XmlPullParser.START_TAG:
	        		String tagName = xpp.getName();
	        		tagStack.push(tagName);
	        		
	        		if (tagName.equals("calendarelement")) {
	        			io.type = TYPE_ARRANGEMENT;
	        			currentLanguage = xpp.getAttributeValue(null, "language");
	        		} else if (tagName.equals("voteelement")) {
	        			io.type = TYPE_POLL;
	        			currentLanguage = xpp.getAttributeValue(null, "language");
	        		} else if(tagName.equals("elementref")) {
	        			nextText = NEXT_TEXT_ELEMENTREF;
	        		} else if (tagName.equals("element")) {
	        			io.type = TYPE_STANDARD;
	        			currentLanguage = xpp.getAttributeValue(null, "language");
	        			currentGeoLocations = new Vector<GeoLocation>();
	        		} else if (tagName.equals("headline")) {
	        			nextText = NEXT_TEXT_HEADLINE;
	        		} else if (tagName.equals("body")) {
	        			nextText = NEXT_TEXT_BODY;
	        		} else if(tagName.equals("geolocation")) {
	        			currentGeoLocation = new GeoLocation();
	        		} else if (tagName.equals("begin")) {
	        			nextText = NEXT_TEXT_ARRANGEMENT_BEGIN;
	        		} else if(tagName.equals("description")) {
	        			nextText = NEXT_TEXT_GEO_LOCATION_DESC;
	        		} else if(tagName.equals("lat")) {
	        			nextText = NEXT_TEXT_GEO_LOCATION_LAT;
	        		} else if (tagName.equals("street")) {
	        			nextText = NEXT_TEXT_GEO_LOCATION_STREET;
	        		} else if (tagName.equals("long")) {
		        		nextText = NEXT_TEXT_GEO_LOCATION_LNG;
	        		} else if (tagName.equals("city")) {
        				nextText = NEXT_TEXT_GEO_LOCATION_CITY;
	        		} else if (tagName.equals("postal")) {
	        			nextText = NEXT_TEXT_GEO_LOCATION_POSTAL;
	        		} else if (tagName.equals("end")) {
	        			nextText = NEXT_TEXT_ARRANGEMENT_END;
	        		} else if (tagName.equals("building")) {
	        			nextText = NEXT_TEXT_GEO_LOCATION_BUILDING;
	        		} else if (tagName.equals("category")) {
	        			nextText = NEXT_TEXT_CATEGORY;
	        		} else if (tagName.equals("name")) {
	        			if (compareStackTops(new String[] {"location", "name"})) {
	        				nextText = NEXT_TEXT_LOCATION;
	        			} else if (compareStackTops(new String[] {"geolocation", "name"})) {
		        			currentGeoLocation = new GeoLocation();
	        				nextText = NEXT_TEXT_GEO_LOCATION_NAME;
	        			}
	        		} else if (tagName.equals("id")) {
	        			if (compareStackTops(new String[] {"element", "id"})) {
	        				nextText = NEXT_TEXT_ID;
	        			} else if (compareStackTops(new String[] {"calendarelement", "id"})) {
							nextText = NEXT_TEXT_ID;
						} else if (compareStackTops(new String[] {"media", "id"})) {
	        				nextText = NEXT_TEXT_MEDIA_ID;
	        			} else if (compareStackTops(new String[] { "voteelement", "id" })) {
	        				nextText = NEXT_TEXT_ID;
	        			}
	        		} else if (tagName.equals("source")) {
	        			if (compareStackTops(new String[] {"urls", "source"})) {
	        				nextText = NEXT_TEXT_MEDIA_SOURCE_URL;
	        			}
	        		} else if (tagName.equals("type")) {
	        			nextText = NEXT_TEXT_MEDIA_TYPE;
	        		} else if (tagName.equals("url")) {
	        			if(compareStackTops(new String[] {"media","url"})) {
	        				nextText = NEXT_TEXT_URL;
	        			}
	        		} else if (tagName.equals("subheadline")) {
	        			nextText = NEXT_TEXT_SUB_HEADLINE;
	        		} else if (tagName.equals("inthepicture")) {
	        			nextText = NEXT_TEXT_MEDIA_IN_THE_PICTURE;
	        		} else if (tagName.equals("artist")) {
	        			nextText = NEXT_TEXT_MEDIA_ARTIST;
	        		} else if (tagName.equals("year")) {
	        			nextText = NEXT_TEXT_MEDIA_YEAR;
	        		} else if (tagName.equals("title")) {
	        			nextText = NEXT_TEXT_MEDIA_TITLE;
	        		} else if (tagName.equals("key")) {
	        			nextText = NEXT_TEXT_META_DATA_KEY;
	        		} else if (tagName.equals("value")) {
	        			nextText = NEXT_TEXT_META_DATA_VALUE;
	        		} else if (tagName.equals("thumb")) {
	        			nextText = NEXT_TEXT_MEDIA_THUMB_URL;
	        		} else if (tagName.equals("description")) {
	        			nextText = NEXT_TEXT_MEDIA_DESCRIPTION;
	        		} else if (tagName.equals("channelid")) {
	        			nextText = NEXT_TEXT_CHANNEL_ID;
	        		} else {
	        			nextText = NEXT_TEXT_NONE;
	        		}
	        		//System.err.println("Entering: " + xpp.getName());
	        		break;
	        	case XmlPullParser.END_TAG:
	        		tagName = xpp.getName();
	        		if(tagName.equals("geolocation")) {
	        			currentGeoLocations.add(currentGeoLocation);
	        			
	        		} else if(tagName.equals("geolocations")) {
	        			io.geoLocations.put(currentLanguage, currentGeoLocations);
	        		}
	        		nextText = NEXT_TEXT_NONE;
	        		if(tagStack.isEmpty()) {
	        			Log.d("", "STACK EMPTY" + tagName);
	        		}
	        		tagStack.pop();
	        		break;
	        	case XmlPullParser.TEXT:
	        		String text = xpp.getText();
	        		//System.err.println("Found text: " + text);
	        		switch (nextText) {
	        		case NEXT_TEXT_ARRANGEMENT_BEGIN:
        				io.arrangementStartTime = DateUtils.parseInfoObjectTime(text);
	        			break;
	        		case NEXT_TEXT_ARRANGEMENT_END:
        				io.arrangementEndTime = DateUtils.parseInfoObjectTime(text);
	        			break;
	        		case NEXT_TEXT_BODY:
	        			io.bodies.put(currentLanguage, text);
	        			break;
	        		case NEXT_TEXT_CATEGORY:
	        			io.category = text;
	        			break;
	        		case NEXT_TEXT_HEADLINE:
	        			io.headlines.put(currentLanguage.trim(), text.trim());
	        			break;
	        		case NEXT_TEXT_ID:
        				AbsoluteId absId = new AbsoluteId(text);
        				io.id = absId.getId();
        				io.customerId = absId.getCustomerId();
        				break;
	        		case NEXT_TEXT_MEDIA_SOURCE_URL:
	        			if (currentMedia!=null) {
	        				try {
	        					currentMedia.setUrl(text);
	        				} catch (MalformedURLException e) {
	        					Log.w(logTag, "Invalid media URL " + text + ". Skipping media.");
	        					io.media.remove(currentMedia);
	        					currentMedia = null;
	        				}
	        			}
	        			break;
	        		case NEXT_TEXT_MEDIA_TYPE:
	        			//String currentMediaType = text;
	        			if (text.equals("image")) {
	        				currentMedia = new Media(Media.TYPE_IMAGE);
	        			} else if (text.equals("video")) {
	        				currentMedia = new Media(Media.TYPE_VIDEO);
	        			} else if (text.equals("audio")) {
	        				currentMedia = new Media(Media.TYPE_AUDIO);
	        			} else {
	        				Log.w(logTag, "Ignoring media of type " + text);
	        				currentMedia = null;
	        			}
	        			break;
	        		case NEXT_TEXT_SUB_HEADLINE:
	        			io.subHeadlines.put(currentLanguage, text);
	        			break;
	        		case NEXT_TEXT_MEDIA_IN_THE_PICTURE:
	        			if (currentMedia!=null) {
	        				currentMedia.setInThePicture(text);
	        			}
	        			break;
	        		case NEXT_TEXT_MEDIA_ARTIST:
	        			if (currentMedia!=null) {
	        				currentMedia.setArtist(text);
	        			}
	        			break;
	        		case NEXT_TEXT_MEDIA_TITLE:
	        			if (currentMedia!=null) {
	        				currentMedia.setTitle(text);
	        			}
	        			break;
	        		case NEXT_TEXT_URL:
	        			if(currentMedia != null) {
	        				currentMedia.setUrl(text);
	        			}
	        			break;
	        		case NEXT_TEXT_MEDIA_YEAR:
	        			if (currentMedia!=null) {
	        				currentMedia.setYear(text);
	        			}
	        			break;
	        		case NEXT_TEXT_MEDIA_ID:
	        			if (currentMedia!=null) {
	        				currentMedia.setId(new AbsoluteId(text));
	        				io.addMedia(currentMedia);
	        			}
	        			break;
	        		case NEXT_TEXT_META_DATA_KEY:
	        			currentMetaDataKey = text;
	        			break;
	        		case NEXT_TEXT_META_DATA_VALUE:
	        			if (currentMetaDataKey!=null) {
	        				io.metaData.put(currentMetaDataKey, text);
	        				currentMetaDataKey = null;
	        			}
	        			break;
	        		case NEXT_TEXT_MEDIA_THUMB_URL:
	        			if (currentMedia!=null) {
	        				currentMedia.setThumbUrl(text);
	        			}
	        			break;
	        		case NEXT_TEXT_MEDIA_DESCRIPTION:
	        			if (currentMedia!=null) {
	        				currentMedia.setDescription(text);
	        			}
	        			break;
	        		case NEXT_TEXT_LOCATION:
	        			io.location = text;
	        			break;
	        		case NEXT_TEXT_GEO_LOCATION_DESC:
	        			if(currentGeoLocation != null) {
	        				currentGeoLocation.setDescription(text);
	        			}
	        			break;
	        		case NEXT_TEXT_GEO_LOCATION_NAME:
	        			if(currentGeoLocation != null) {
	        				currentGeoLocation.setName(text);
	        			}
	        			break;
	        		case NEXT_TEXT_GEO_LOCATION_LAT:
	        			if(currentGeoLocation != null) {
	        				currentGeoLocation.setLatitude(Float.valueOf(text));
	        			}
	        			break;
	        		case NEXT_TEXT_GEO_LOCATION_LNG:
	        			if(currentGeoLocation != null) {
	        				currentGeoLocation.setLongitude(Float.valueOf(text));
	        			}
	        			break;
	        		case NEXT_TEXT_GEO_LOCATION_STREET:
	        			if(currentGeoLocation != null) {
	        				currentGeoLocation.setStreet(text);
	        			}
	        			break;
	        		case NEXT_TEXT_GEO_LOCATION_CITY:
	        			if (currentGeoLocation != null) {
	        				currentGeoLocation.setCity(text);
	        			}
	        			break;
	        		case NEXT_TEXT_GEO_LOCATION_POSTAL:
	        			if (currentGeoLocation != null) {
	        				currentGeoLocation.setPostal(text);
	        			}
	        			break;
	        		case NEXT_TEXT_GEO_LOCATION_BUILDING:
	        			if (currentGeoLocation != null) {
	        				currentGeoLocation.setBuilding(text);
	        			}
	        			break;
	        		case NEXT_TEXT_ELEMENTREF:
	        			io.addChildId(text);
	        			break;
	        		case NEXT_TEXT_CHANNEL_ID:
	        			io.addChannelId(text);
	        			break;
	        		case NEXT_TEXT_NONE:
	        			//System.err.println("Don't want next text:" + text);
	        			break;
	        		}
	        		break;
	        	case XmlPullParser.START_DOCUMENT:
	        		//System.err.println("Starting document");
	        		break;
        		default:
        			continue;
//        			System.err.println("Unknown event type: " + eventType);
	        	}
	        	xpp.next();
	        }
	        
		} catch (XmlPullParserException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return io;
	}
	
	public String getHeadline(String language) {
		if (this.headlines.containsKey(language)) {
			return headlines.get(language);
		}
		return null;
	}
	
	public String getId() {
		return this.id;
	}
	
	public String getCustomerId() {
		return this.customerId;
	}
	
	private static boolean compareStackTops(String[] topOfOtherStack) {
		String[] tagStackArray = new String[tagStack.size()];
		tagStack.toArray(tagStackArray);
		for (int i=0;i<topOfOtherStack.length;i++) {
			int otherStackIdx = topOfOtherStack.length - i - 1;
			int myStackIdx = tagStackArray.length - i - 1;
			if (!tagStackArray[myStackIdx].equals(topOfOtherStack[otherStackIdx])) {
				return false;
			}
		}

		return true;
	}
	
	public String getXml() {
		return this.xmlSource;
	}

	public AbsoluteId getAbsoluteId() {
		return new AbsoluteId(this.customerId, this.id);
	}

	public int getType() {
		return this.type;
	}

	public Media getFirstImageMedia() {
		for (Media media : this.media) {
			if (media.getType()==Media.TYPE_IMAGE) {
				return media;
			}
		}
		return null;
	}
	
	public Media getFirstVideoMedia() {
		for (Media media : this.media) {
			if (media.getType()==Media.TYPE_VIDEO) {
				return media;
			}
		}
		return null;
	}
	
	public List<String> getChildIds() {
		return childIds;
	}

	public String getBody(String language) {
		return bodies.get(language);
	}
	
	public String getSubHeadline(String language) {
		return subHeadlines.get(language);
	}

	public Date getArrangementEndTime() {
		return arrangementEndTime;
	}
	
	public Date getArrangementStartTime() {
		return arrangementStartTime;
	}

	public boolean hasCategory(String targetCategory) {
		if (targetCategory!=null && this.category!=null) {
			int res = this.category.compareToIgnoreCase(targetCategory);
			return res==0;
		} else {
			return false;
		}
	}
	
	public boolean hasMetaDataKey(String key) {
		return this.metaData.containsKey(key);
	}
	
	public String getMetaData(String key) {
		return this.metaData.get(key);
	}

	public String getLocation() {
		return this.location;
	}
	
	public List<GeoLocation> getGeoLocations(String language) {
		List<GeoLocation> result = new ArrayList<GeoLocation>();
		Vector<GeoLocation> locations = geoLocations.get(language);
		for(GeoLocation location : locations) {
			result.add(location);
		}
		return result;
	}
	
	public int describeContents() {
		// TODO Auto-generated method stub
		return 0;
	}
	
	/**
	 * Get a list of media matching the specified types.
	 * 
	 * @param type Bitmask defining the types of media requested. OR Media.TYPE* values together to form the bitmask.
	 * @return The requested (possibly empty) list of media
	 */
	public List<Media> getMedia(int type) {
		ArrayList<Media> result = new ArrayList<Media>();
		for (Media m : this.media) {
			if ((m.getType() & type)>0) {
				result.add(m);
			}
		}
		return result;
	}
	
	public List<String> getChannelIds() {
		return channelIds;
	}
	
	public void addChannelId(String id) {
		for(String s : channelIds) {
			if(id.equals(s)){
				return;
			}
		}
		channelIds.add(id);
	}
 	
	public void addMedia(Media m) {
		// Ensure that no duplicates are inserted into the list
		for (Media currentMedia : media) {
			if (m.getId().equals(currentMedia.getId())) {
				return;
			}
		}
		media.add(m);
	}
	
	public void addChildId(String id) {
		for(String s : childIds) {
			if(id.equals(s)){
				return;
			}
		}
		childIds.add(id);
	}
}
